feat: add accessibility adaptation layer#4924
feat: add accessibility adaptation layer#4924adrcotfas wants to merge 1 commit into@adrcotfas/refactor/tokens_elevationfrom
Conversation
|
Hey @adrcotfas, thank you for your pull request 🤗. The documentation from this branch can be viewed here. |
|
The mobile version of example app from this branch is ready! You can see it here. |
b8209f3 to
b4a5696
Compare
| toRawSpring, | ||
| } from './theme/tokens/sys/motion'; | ||
|
|
||
| export { useAccessibleTheme } from './theme/accessibility'; |
There was a problem hiding this comment.
Don't think this belongs as a public API. We should only export what's absolutely necessary. Avoid expanding the API surface unless really needed.
| /** @deprecated Will be removed in a future version. MD3 uses tonal surface colors via `theme.colors.elevation.*`. */ | ||
| mode?: Mode; | ||
| /** @deprecated Use `theme.shapes.*` instead. Will be removed in a future version. */ | ||
| roundness: number; | ||
| /** @deprecated Use `theme.motion.*` instead. Will be removed in a future version. */ | ||
| animation: { | ||
| /** @deprecated Use `theme.motion.prefersReducedMotion` instead. Will be removed in a future version. */ | ||
| scale: number; | ||
| /** @deprecated Use `theme.motion.duration.*` instead. Will be removed in a future version. */ | ||
| /** @deprecated No-op. Use `theme.motion.duration.*` instead. Will be removed in a future version. */ | ||
| defaultAnimationDuration?: number; |
There was a problem hiding this comment.
Remove deprecated theme properties. We don't want another situation like V2/v3 themes.
If necessary, the migration guide can show a helper that converts old theme to new theme for easier migration.
| export { | ||
| expressiveMotion, | ||
| standardMotion, | ||
| reducedMotion, |
There was a problem hiding this comment.
I'm not sure it's necessary to expose this.
| export const reducedMotion: MotionConfig = { | ||
| spring: { | ||
| fast: { spatial: instantSpring, effects: instantSpring }, | ||
| default: { spatial: instantSpring, effects: instantSpring }, | ||
| slow: { spatial: instantSpring, effects: instantSpring }, | ||
| }, | ||
| easing: motionEasing, | ||
| prefersReducedMotion: true, | ||
| duration: { | ||
| short1: 0, | ||
| short2: 0, | ||
| short3: 0, | ||
| short4: 0, | ||
| medium1: 0, | ||
| medium2: 0, | ||
| medium3: 0, | ||
| medium4: 0, | ||
| long1: 0, | ||
| long2: 0, | ||
| long3: 0, | ||
| long4: 0, | ||
| extraLong1: 0, | ||
| extraLong2: 0, | ||
| extraLong3: 0, | ||
| extraLong4: 0, | ||
| }, | ||
| }; |
There was a problem hiding this comment.
I'm not sure setting all of these to 0/instant animation makes sense for reduce motion. Reduce motion is about reducing motion (e.g., movement, like translation, scale, etc.). Simple animations like opacity and colors should still work. It shouldn't disable animations entirely.
It probably makes more sense to handle it at the component level, because how each component changes its animation in response to this depends on the component, e.g. menu should change to simple fade instead of scale etc.
| }; | ||
| } | ||
|
|
||
| export function useAccessibleTheme(theme: Theme, enabled = true): Theme { |
There was a problem hiding this comment.
I don't think this should be wrapping theme in useTheme. This causes every component using this hook to subscribe to reduce motion.
If this is handled, then it should be done in PaperProvider in a single place so there's a single subscription.
But also, if prefersReducedMotion is in the theme, it conflicts with such an API if it's also handled internally. When the user passes a theme, which will already have prefersReducedMotion, is it also handled internally and overridden?
It probably makes sense to expose a separate prop reduceMotion="auto" or something like that on PaperProvider, then internally it can subscribe and expose the actual value in context. Then, on the component level, components can adapt their animations to reduce motion when necessary.
There is also the problem of the value not being known on initial render, as it's async, so if some component has animation on render, it won't properly respect this.
Motivation
The library's existing reduce-motion support set
animation.scale = 0-- a v2-era flag that components had to explicitly check, with no connection to the MD3theme.motionspring and duration tokens.This PR wires OS accessibility into the theme properly.
useAccessibleThemereplaces the inlinePaperProviderlogic, subscribes toreduceMotionChanged, and when active collapsestheme.motionto instant springs and zero durations via the newreducedMotionpreset. A cleantheme.motion.prefersReducedMotion: booleanflag gives components a single readable signal instead of the magic scale === 0 check.animation.scaleis kept and still set to 0 for backward compatibility with components that haven't migrated yet. It is now formally deprecated in favour oftheme.motion.prefersReducedMotion.Two further deprecations are added while here:
theme.mode('adaptive' | 'exact'), which is an MD2 elevation-overlay concept superseded by tonal surface colors intheme.colors.elevation.*; andanimation.defaultAnimationDuration, which was never read at runtime.No component behavior changes. The reduce-motion path is functionally equivalent to before for all existing components. theme.motion.prefersReducedMotion and the reducedMotion preset are foundation plumbing -- they will be consumed when components migrate to
theme.motiontokens in the per-component PRs that follow.Related issue
See https://www.notion.so/callstack/React-Native-Paper-Foundation-for-MD3-Expressive-34c5d027c0f880edba3df107cd35946f?source=copy_link
Merge order:
Test plan
yarn typescript-- no new type errorsyarn test-- all tests pass